package com.dkd.service.impl;

import cn.elegent.ac.ElegentAC;
import cn.elegent.actx.annotation.ACTransaction;
import cn.elegent.actx.context.ElegentACTXContext;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dkd.config.ACTXConfig;
import com.dkd.config.TopicConfig;
import com.dkd.config.constant.SystemKeys;
import com.dkd.config.constant.VmStatus;
import com.dkd.contract.SupplyChannel;
import com.dkd.contract.SupplyContract;
import com.dkd.contract.VendoutContract;
import com.dkd.dao.VendingMachineDao;
import com.dkd.entity.*;
import com.dkd.exception.LogicException;
import com.dkd.http.vo.CreateVMReq;
import com.dkd.service.*;
import com.dkd.utils.UUIDUtils;
import com.dkd.viewModel.VmInfoDTO;
import com.dkd.viewModel.VmSearch;
import com.dkd.vo.Pager;
import com.dkd.vo.SkuVO;
import com.dkd.vo.VmVO;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class VendingMachineServiceImpl extends ServiceImpl<VendingMachineDao, VendingMachineEntity> implements VendingMachineService {

    @Autowired
    private NodeService nodeService;

    @Autowired
    private ChannelService channelService;

    @Autowired
    private VmTypeService vmTypeService;

    @Autowired
    private VendoutRunningService vendoutRunningService;

    @Autowired
    private ElegentAC elegentAC;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient esClient;

    /**
     * 处理出货逻辑
     *
     * @param vendoutContract
     * @return
     */
    @Override
    @ACTransaction(mainName = ACTXConfig.ORDER_VENDOUT,
            childName = ACTXConfig.ORDER_VENDOUT_DED)
    public boolean vendout(VendoutContract vendoutContract) {
        //获取管道列表
        List<ChannelEntity> channelList = this.channelService.getChannelesByInnerCode(vendoutContract.getInnerCode());
        //查询商品所在的管道
        Optional<ChannelEntity> channel = channelList.stream()
                .filter(c -> c.getCurrentCapacity() > 0)
                .filter(c -> c.getSkuId() == vendoutContract.getVendoutData().getSkuId())
                .findFirst();
        if (channel.isPresent()) {
            //更新货道库存
            //提出货道数据
            ChannelEntity channelEntity = channel.get();
            channelEntity.setCurrentCapacity(channelEntity.getCurrentCapacity() - 1);
            this.channelService.updateById(channelEntity);

            //存入出货流水数据
            VendoutRunningEntity vendoutRunningEntity = new VendoutRunningEntity();
            vendoutRunningEntity.setOrderNo(vendoutContract.getVendoutData().getOrderNo());
            vendoutRunningEntity.setInnerCode(vendoutContract.getInnerCode());
            vendoutRunningEntity.setStatus(false);
            vendoutRunningEntity.setSkuId(vendoutContract.getVendoutData().getSkuId());
            //保存
            this.vendoutRunningService.save(vendoutRunningEntity);

            //将货到编号记录到发货协议
            vendoutContract.getVendoutData().setChannelCode(channelEntity.getChannelCode());
            //保存快照
            ElegentACTXContext.setSnapshot("vendoutContract", vendoutContract);
            return this.elegentAC.publish(TopicConfig.getVendoutTopic(vendoutContract.getInnerCode()), vendoutContract);
        } else {
            return false;
        }

    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean add(CreateVMReq vendingMachine) {
        VendingMachineEntity vendingMachineEntity = new VendingMachineEntity();
        vendingMachineEntity.setNodeId(Long.valueOf(vendingMachine.getNodeId()));
        vendingMachineEntity.setVmType(vendingMachine.getVmType());
        NodeEntity nodeEntity = nodeService.getById(vendingMachine.getNodeId());
        if (nodeEntity == null) {
            throw new LogicException("所选点位不存在");
        }
        //复制属性
        BeanUtils.copyProperties(nodeEntity, vendingMachineEntity);
        vendingMachineEntity.setCreateUserId(Long.valueOf(vendingMachine.getCreateUserId()));
        vendingMachineEntity.setInnerCode(UUIDUtils.getUUID());
        vendingMachineEntity.setClientId(UUIDUtils.generateClientId(vendingMachineEntity.getInnerCode()));
        this.save(vendingMachineEntity);
        //创建货道数据
        createChannel(vendingMachineEntity);
        return true;
    }


    /**
     * 创建货道
     *
     * @param vm
     * @return
     */
    private boolean createChannel(VendingMachineEntity vm) {
        VmTypeEntity vmType = vmTypeService.getById(vm.getVmType());
        List<ChannelEntity> channelList = Lists.newArrayList();
        for (int i = 1; i <= vmType.getVmRow(); i++) {
            for (int j = 1; j <= vmType.getVmCol(); j++) {
                ChannelEntity channel = new ChannelEntity();
                channel.setChannelCode(i + "-" + j);
                channel.setCurrentCapacity(0);
                channel.setInnerCode(vm.getInnerCode());
                channel.setLastSupplyTime(vm.getLastSupplyTime());
                channel.setMaxCapacity(vmType.getChannelMaxCapacity());
                channel.setVmId(vm.getId());
                channelList.add(channel);
            }
        }
        channelService.saveBatch(channelList);
        return true;
    }


    @Override
    public boolean update(Long id, Long nodeId) {
        VendingMachineEntity vm = this.getById(id);
        if (vm.getVmStatus() == VmStatus.VM_STATUS_RUNNING)
            throw new LogicException("改设备正在运营");
        NodeEntity nodeEntity = nodeService.getById(nodeId);
        BeanUtils.copyProperties(nodeEntity, vm);
        return this.updateById(vm);
    }


    @Override
    public Pager<String> getAllInnerCodes(boolean isRunning, long pageIndex, long pageSize) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<VendingMachineEntity> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageIndex, pageSize);

        QueryWrapper<VendingMachineEntity> qw = new QueryWrapper<>();
        if (isRunning) {
            qw.lambda()
                    .select(VendingMachineEntity::getInnerCode)
                    .eq(VendingMachineEntity::getVmStatus, 1);
        } else {
            qw.lambda()
                    .select(VendingMachineEntity::getInnerCode)
                    .ne(VendingMachineEntity::getVmStatus, 1);
        }
        this.page(page, qw);
        Pager<String> result = new Pager<>();
        result.setCurrentPageRecords(page.getRecords().stream().map(VendingMachineEntity::getInnerCode).collect(Collectors.toList()));
        result.setPageIndex(page.getCurrent());
        result.setPageSize(page.getSize());
        result.setTotalCount(page.getTotal());

        return result;
    }

    @Override
    public Pager<VendingMachineEntity> query(Long pageIndex, Long pageSize, Integer status, String innerCode) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<VendingMachineEntity> page
                = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<VendingMachineEntity> queryWrapper = new LambdaQueryWrapper<>();
        if (status != null) {
            queryWrapper.eq(VendingMachineEntity::getVmStatus, status);
        }
        if (!Strings.isNullOrEmpty(innerCode)) {
            queryWrapper.likeLeft(VendingMachineEntity::getInnerCode, innerCode);
        }
        this.page(page, queryWrapper);

        return Pager.build(page);
    }


    @Override
    public VmVO findByInnerCode(String innerCode) {
        LambdaQueryWrapper<VendingMachineEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(VendingMachineEntity::getInnerCode, innerCode);
        VendingMachineEntity vm = this.getOne(queryWrapper);
        VmVO vmVO = new VmVO();
        BeanUtils.copyProperties(vm, vmVO);
        vmVO.setNodeAddr(vm.getNode().getAddr());//地址
        vmVO.setNodeName(vm.getNode().getName());//名称
        return vmVO;
    }


    @Override
    public boolean updateStatus(String innerCode, Integer status) {
        UpdateWrapper<VendingMachineEntity> uw = new UpdateWrapper<>();
        uw.lambda()
                .eq(VendingMachineEntity::getInnerCode, innerCode)
                .set(VendingMachineEntity::getVmStatus, status);
        this.update(uw);
        return true;
    }


    @Override
    public List<SkuVO> getSkuListByInnerCode(String innerCode) {
        //获取货道列表
        List<ChannelEntity> channelList = channelService.getChannelesByInnerCode(innerCode).stream()
                .filter(c -> c.getSkuId() > 0 && c.getSku() != null).collect(Collectors.toList());
        //获取有商品的库存余量
        Map<SkuEntity, Integer> skuMap = channelList.stream()
                .collect(Collectors.groupingBy(
                        ChannelEntity::getSku,
                        Collectors.summingInt(ChannelEntity::getCurrentCapacity)));//对库存数求和
        return skuMap.entrySet().stream().map(entry -> {
            SkuEntity sku = entry.getKey(); //查询商品
            SkuVO skuVO = new SkuVO();
            BeanUtils.copyProperties(sku, skuVO);
            skuVO.setImage(sku.getSkuImage());//图片
            skuVO.setCapacity(entry.getValue());
            skuVO.setRealPrice(sku.getPrice());//真实价格
            return skuVO;
        }).sorted(Comparator.comparing(SkuVO::getCapacity).reversed())  //按库存量降序排序
                .collect(Collectors.toList());
    }

    @Override
    public Boolean hasCapacity(String innerCode, Long skuId) {
        var qw = new LambdaQueryWrapper<ChannelEntity>();
        qw
                .eq(ChannelEntity::getInnerCode, innerCode)
                .eq(ChannelEntity::getSkuId, skuId)
                .gt(ChannelEntity::getCurrentCapacity, 0);
        return channelService.count(qw) > 0;
    }

    @Override
    public int countCapacity(String innerCode, Long skuId) {
        var qw = new QueryWrapper<ChannelEntity>();
        qw
                .select("sum(current_capacity  ) as current_capacity")
                .lambda()
                .eq(ChannelEntity::getInnerCode, innerCode)
                .eq(ChannelEntity::getSkuId, skuId);
        List<ChannelEntity> list = channelService.list(qw);
        if (list.get(0) != null) {
            return list.get(0).getCurrentCapacity();
        } else {
            return 0;
        }
    }

    @Override
    public int countMaxCapacity(String innerCode, Long skuId) {
        var qw = new QueryWrapper<ChannelEntity>();
        qw
                .select("sum(max_capacity  ) as max_capacity")
                .lambda()
                .eq(ChannelEntity::getInnerCode, innerCode)
                .eq(ChannelEntity::getSkuId, skuId);
        List<ChannelEntity> list = channelService.list(qw);
        if (list.get(0) != null) {
            return list.get(0).getMaxCapacity();
        } else {
            return 0;
        }
    }

    @Override
    public int countCapacityRatio(String innerCode, Long skuId) {
        int maxCapacity = countMaxCapacity(innerCode, skuId); //获取最大库存
        if (maxCapacity <= 0) {
            return 0;
        }
        int capacity = countCapacity(innerCode, skuId);  //获得当前库存
        if (capacity <= 0) {
            return 0;
        }
        return (new BigDecimal(capacity).multiply(new BigDecimal(100)).divide(new BigDecimal(maxCapacity), 2, RoundingMode.HALF_UP)).intValue();
    }

    @Override
    public VendingMachineEntity findByClientId(String clientId) {
        QueryWrapper<VendingMachineEntity> qw = new QueryWrapper<>();
        qw.lambda().eq(VendingMachineEntity::getClientId, clientId);
        return this.getOne(qw);
    }


    @Override
    public boolean supply(SupplyContract supply) {
        //更新最后补货时间
        UpdateWrapper<VendingMachineEntity> uw = new UpdateWrapper<>();
        uw.lambda()
                .eq(VendingMachineEntity::getInnerCode, supply.getInnerCode())
                .set(VendingMachineEntity::getLastSupplyTime, LocalDateTime.now());
        this.update(uw);
        //按照售货机编号查询货道列表
        List<ChannelEntity> channelList = channelService.getChannelesByInnerCode(supply.getInnerCode());
        supply.getSupplyData()
                .forEach(
                        c -> {
                            //通过stream流筛选货道，比在数据库中查询性能高
                            Optional<ChannelEntity> item =
                                    channelList.stream()
                                            .filter(channel -> channel.getChannelCode().equals(c.getChannelId()))
                                            .findFirst();
                            if (item.isPresent()) {
                                var channelEntity = item.get();
                                //更新货道库存
                                channelEntity.setCurrentCapacity(channelEntity.getCurrentCapacity() + c.getCapacity());
                                //更新货道最后补货时间
                                channelEntity.setLastSupplyTime(LocalDateTime.now());
                                channelService.updateById(channelEntity);
                            }
                        });
        return true;
    }

    @Override
    public SupplyContract buildSupplyContract(String innerCode) {
        //查询售货机的货道列表（ skuId!=0 ）
        QueryWrapper<ChannelEntity> channelQueryWrapper = new QueryWrapper<>();
        channelQueryWrapper.lambda()
                .eq(ChannelEntity::getInnerCode, innerCode)//售货机Id
                .ne(ChannelEntity::getSkuId, 0L);
        List<ChannelEntity> channelList = channelService.list(channelQueryWrapper);
        //补货列表
        List<SupplyChannel> supplyChannelList = channelList.stream()
                .filter(c -> c.getCurrentCapacity() < c.getMaxCapacity()) //筛选缺货货道
                .map(c -> {
                    SupplyChannel supplyChannel = new SupplyChannel();
                    supplyChannel.setChannelId(c.getChannelCode());//货道编号
                    supplyChannel.setCapacity(c.getMaxCapacity() - c.getCurrentCapacity());//补货
                    supplyChannel.setSkuId(c.getSkuId());// 商品Id
                    supplyChannel.setSkuName(c.getSku().getSkuName());//商品名称
                    supplyChannel.setSkuImage(c.getSku().getSkuImage());//商品图片
                    return supplyChannel;
                }).collect(Collectors.toList());
        SupplyContract supplyContract = new SupplyContract();
        supplyContract.setInnerCode(innerCode);
        supplyContract.setSupplyData(supplyChannelList);
        return supplyContract;
    }


    /**
     * 根据条件搜索售货机
     *
     * @param searchReq@return
     */
    @Override
    public List<VmInfoDTO> search(VmSearch searchReq) {
        //1.封装查询条件
        SearchRequest searchRequest = new SearchRequest("vm");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("vmstatus", "1"))
                .must(new GeoDistanceQueryBuilder("location")
                        //设置半径
                        .distance(searchReq.getDistance(), DistanceUnit.KILOMETERS)
                        //设置圆心
                        .point(searchReq.getLat(), searchReq.getLon())));
        //排序（由近到远）
        GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder("location", searchReq.getLat(), searchReq.getLon());

        geoDistanceSortBuilder.unit(DistanceUnit.KILOMETERS);
        geoDistanceSortBuilder.order(SortOrder.ASC);
        geoDistanceSortBuilder.geoDistance(GeoDistance.ARC);
        sourceBuilder.sort(geoDistanceSortBuilder);
        searchRequest.source(sourceBuilder);

        //2.封装查询结果
        try {
            SearchResponse searchResponse = esClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = searchResponse.getHits();
            if (hits.getTotalHits().value <= 0) {
                return Lists.newArrayList();
            }
            List<VmInfoDTO> vmInfoDTOList = Lists.newArrayList();
            Arrays.stream(hits.getHits()).forEach(hit -> {
                VmInfoDTO vmInfoDTO = null;
                try {
                    String vmJson = hit.getSourceAsString();
                    vmInfoDTO = JSON.parseObject(vmJson, VmInfoDTO.class);
                    //查询结果距离圆心的长度
                    vmInfoDTO.setDistance((Integer) hit.getSortValues()[0]);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (vmInfoDTO != null) {
                    vmInfoDTOList.add(vmInfoDTO);
                }
            });
            return vmInfoDTOList;
        } catch (Exception e) {
            e.printStackTrace();
            return Lists.newArrayList();
        }
    }

    @Override
    public void updateOnLineStatus(String innerCode, Boolean status) {
        Map statusMap = (Map) redisTemplate.boundHashOps(SystemKeys.VM_STATUES_KEY).get(innerCode);
        if (statusMap == null) statusMap = new HashMap();
        statusMap.put("10001", status);//10001 断网断电   10002 货道   10003 传送轴
        redisTemplate.boundHashOps(SystemKeys.VM_STATUES_KEY).put(innerCode, statusMap);
    }

}
